package cn.bearspark.api.util;

import cn.bearspark.api.vo.HtmlTextUnparseable;
import cn.bearspark.api.vo.main.user.UserAtVO;
import cn.bearspark.common.dto.DelayPublishable;
import cn.bearspark.common.dto.Publishable;
import cn.bearspark.common.parse.html.*;
import cn.bearspark.common.util.ConvertBeanUtil;
import cn.bearspark.emoji.dto.AddEmojisDTO;
import cn.bearspark.emoji.rpc.EmojiRpc;
import cn.bearspark.user.dto.UserAtDTO;
import cn.bearspark.user.rpc.UserAtRpc;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Supplier;

/**
 * 可以解析出 @ 的用户名 和 使用的表情包 的数据的工具类
 *
 * @author f
 */
@Component
public class ParseableDataUtil {

    /**
     * 解析 html 文本的管道
     */
    private final HtmlPipeline htmlPipeline = (HtmlPipeline) new HtmlPipeline()
            .add(new TagBrParser())
            .add(new TagDivParser())
            .add(new TagSpanParser())
            .add(new TagImgParser());

    @DubboReference
    private UserAtRpc userAtRpc;
    @DubboReference
    private EmojiRpc emojiRpc;

    /**
     * 发布一段文本
     *
     * @param moduleCode  模块的枚举值
     * @param publisher   发布器
     * @param publishable 可发布的文本对象
     * @param <T>         发布内容的类型
     */
    public <T extends Publishable> void publish(int moduleCode, Supplier<Long> publisher, T publishable) {
        // 解析文本内容
        HtmlTextParseContext ctx = htmlPipeline.parse(publishable.getContent());

        // 将原文本替换成解析后的文本
        publishable.setContent(ctx.getBody());

        // 发布，获取该数据的主键 id
        long moduleDataId = publisher.get();
        List<String> atUsernameList = ctx.getAtUsernameList();

        // 保存文本中使用的表情包和 @ 的用户
        save(moduleCode, moduleDataId, atUsernameList, ctx.getEmojiUrlList());

        // 通知被 @ 的用户
        notifyAtUser(atUsernameList, null);
    }

    /**
     * 延迟发布一段文本
     *
     * @param moduleCode       模块的枚举值
     * @param publisher        发布器
     * @param delayPublishable 可延迟发布的文本对象
     * @param <T>              发布内容的类型
     */
    public <T extends DelayPublishable> void publish(int moduleCode, Supplier<Long> publisher, T delayPublishable) {
        // 解析文本内容
        HtmlTextParseContext ctx = htmlPipeline.parse(delayPublishable.getContent());

        // 将原文本替换成解析后的文本
        delayPublishable.setContent(ctx.getBody());

        // 发布，获取该数据的主键 id
        long moduleDataId = publisher.get();
        List<String> atUsernameList = ctx.getAtUsernameList();

        // 保存文本中使用的表情包和 @ 的用户
        save(moduleCode, moduleDataId, atUsernameList, ctx.getEmojiUrlList());

        // 通知被 @ 的用户
        LocalDateTime pubTime = delayPublishable.getPubTime();
        if (LocalDateTime.now().isAfter(pubTime)) {
            // 如果 当前时间 晚于 发布时间，则立即通知被 @ 的用户
            notifyAtUser(atUsernameList, null);
        } else {
            // 如果 当前时间 早于 发布时间，则延迟通知被 @ 的用户
            notifyAtUser(atUsernameList, pubTime);
        }
    }

    // 保存文本中使用的表情包和 @ 的用户
    private void save(int moduleCode, long moduleDataId, List<String> atUsernameList, List<String> emojiUrlList) {
        // 保存 @ 用户名信息
        if (!atUsernameList.isEmpty()) {
            userAtRpc.addAtUsernameList(moduleCode, moduleDataId, atUsernameList);
        }

        // 保存表情包信息
        if (!emojiUrlList.isEmpty()) {
            AddEmojisDTO addEmojisDTO = new AddEmojisDTO();
            addEmojisDTO.setModuleCode(moduleCode);
            addEmojisDTO.setModuleDataId(moduleDataId);
            addEmojisDTO.setEmojiUrlList(emojiUrlList);
            emojiRpc.addEmojiList(addEmojisDTO);
        }
    }

    // TODO 通知被 @ 的用户，把延迟通知放到消息模块，然后增加一个回调接口 onDeadline()，用于触发这个通知
    private void notifyAtUser(List<String> atUsernameList, LocalDateTime pubTime) {
    }

    /**
     * 反解析指定模块下的待解析对象集合，将必要的信息放到每个待解析的对象中
     *
     * @param moduleCode    模块的枚举值
     * @param toUnparseList 待解析对象集合
     */
    public <T extends HtmlTextUnparseable> void unparse(int moduleCode, List<T> toUnparseList) {
        // 用于查询 @ 用户名的 id 集合
        Set<Long> atIdSet = new HashSet<>();
        // 用于查询表情包的 id 集合
        Set<Long> emojiIdSet = new HashSet<>();

        // 获取需要查询的 @ 用户名 id 和 表情包 id
        toUnparseList.forEach(toUnparse -> {
            String content = toUnparse.getContent();
            if (content == null) {
                return;
            }
            long id = toUnparse.id();
            // 只有当内容中含有 span 标签时，才需要查询它 @ 的用户名
            if (htmlPipeline.containsSpan(content)) {
                atIdSet.add(id);
            }
            // 只有当内容中含有 img 标签时，才需要查询它的表情包
            if (htmlPipeline.containsImg(content)) {
                emojiIdSet.add(id);
            }
        });

        // 获取 @ 的用户信息
        Map<Long, List<UserAtDTO>> atUserInfoMap = !atIdSet.isEmpty() ?
                userAtRpc.mapAtUsernameSet(moduleCode, atIdSet) :
                new HashMap<>();

        // 获取表情包
        Map<Long, List<String>> emojiUrlMap = !emojiIdSet.isEmpty() ?
                emojiRpc.mapEmojiSet(moduleCode, emojiIdSet) :
                new HashMap<>();

        // 进行反解析
        toUnparseList.forEach(toUnparse -> {
            String content = toUnparse.getContent();
            if (content == null) {
                return;
            }

            // 反解析文本
            long id = toUnparse.id();
            HtmlTextParseContext ctx = new HtmlTextParseContext();
            ctx.setEmojiUrlList(emojiUrlMap.get(id));
            ctx.setBody(content);

            // 设置反解析得到的信息
            List<UserAtDTO> userAtDTOList = atUserInfoMap.get(id);
            toUnparse.setContent(htmlPipeline.unparse(ctx));
            toUnparse.setAtUserInfoList(userAtDTOList == null ? new ArrayList<>() : userAtDTOList.stream().map(
                    userAtDTO -> ConvertBeanUtil.convert(userAtDTO, UserAtVO.class)).toList());
        });
    }

    /**
     * 在清理完基础数据后，删除带有的 @ 的用户数据 和 表情包数据
     *
     * @param moduleCode   模块的枚举值
     * @param moduleDataId 数据的主键 id
     */
    public void afterDeleteBase(int moduleCode, long moduleDataId) {
        // 删除 动态使用的表情包
        emojiRpc.delete(moduleCode, moduleDataId);

        // 删除 动态 @ 的用户
        userAtRpc.delete(moduleCode, moduleDataId);
    }

}
